package com.yunxin.common.utils;

import cn.hutool.core.text.CharSequenceUtil;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.comm.ResponseMessage;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.event.ProgressListener;
import com.aliyun.oss.model.*;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.auth.sts.AssumeRoleRequest;
import com.aliyuncs.auth.sts.AssumeRoleResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.yunxin.common.utils.oss.OssPhotoOption;
import com.yunxin.common.utils.oss.OssPhotoOptionsParser;
import com.yunxin.common.utils.oss.OssStreamDownloadLineProcessor;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.URL;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Formatter;
import java.util.List;
import java.util.Objects;

/**
 * 阿里云oss工具
 * @author zero°
 * @since 2023-06-05
 */
@Slf4j
public class AliYunOssUtils {


    private volatile static OSS ossClient;

    private volatile static DefaultAcsClient acsClient;

    /**
     * 获取sts ，允许上传权限
     */
    private static final String POLICY = "{\"Version\":\"1\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"oss:Put*\"],\"Resource\":[\"acs:oss:*:*:bucketName/*\"]}]}";

    private static final String STS_ENDPOINT = "sts.aliyuncs.com";

    private static final String ROLE_SESSION_NAME = "FileUploadAssistantClient";

    private static final int MAX_DELETE_OBJECTS = 1000;
    // 设置超时时间为60秒
    private static final int connectTimeout = 60;
    private static final int readTimeout = 60;

    /**
     * @param endpoint        OSS端点域名
     * @param accessKeyId     OSS的API KEY
     * @param accessKeySecret OSS的API密钥
     */
    public AliYunOssUtils(String endpoint, String accessKeyId, String accessKeySecret) {
        if (Objects.isNull(ossClient)) {

            synchronized (AliYunOssUtils.class) {
                if (Objects.isNull(ossClient)) {
                    ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
                    IClientProfile profile = DefaultProfile.getProfile("", accessKeyId, accessKeySecret);
                    try {
                        DefaultProfile.addEndpoint("", "", "Sts", STS_ENDPOINT);
                    } catch (ClientException e) {
                    }
                    acsClient = new DefaultAcsClient(profile);
                }
            }
        }
//        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
//        IClientProfile profile = DefaultProfile.getProfile("", accessKeyId, accessKeySecret);
//        try {
//            DefaultProfile.addEndpoint("", "", "Sts", STS_ENDPOINT);
//        } catch (ClientException e) {
//        }
//        acsClient = new DefaultAcsClient(profile);


    }


    /**
     * 下载指定 存储空间（Bucket）下的文件
     *
     * @param bucketName  存储空间
     * @param ossFilePath oss中的文件路径，包含文件名 例如abc/efg/123.jpg
     * @param fileUrl     下载到本地的路径，例如 D://oss/
     * @param fileName    文件名称：aa.csv
     */
    public void download(String bucketName, String ossFilePath, String fileUrl, String fileName) {
        // 下载OSS文件到本地文件。如果指定的本地文件存在会覆盖，不存在则新建。
        ossClient.getObject(new GetObjectRequest(bucketName, ossFilePath), new File(String.format("%s%s", fileUrl, fileName)));
    }

    /**
     * 流式下载
     *
     * @param bucketName  存储空间
     * @param ossFilePath oss中的文件路径，包含文件名 例如abc/efg/123.jpg
     * @return 流，一定要手动关闭流，可以使用 try-with-resources语句
     */
    public InputStream download(String bucketName, String ossFilePath) {
        // ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
        OSSObject ossObject = ossClient.getObject(bucketName, ossFilePath);
        return ossObject.getObjectContent();
    }

    /**
     * 上传文件到oss
     * @param bucketName  存储桶名称
     * @param ossFilePath oss中的文件路径，包含文件名 例如abc/efg/123.jpg
     * @param input       文件流
     */
    public boolean upload(String bucketName, String ossFilePath, InputStream input) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(
                bucketName,
                ossFilePath,
                input);
        return upload(putObjectRequest);
    }

    /**
     * 上传文件到oss
     *
     * @param bucketName  存储桶名称
     * @param ossFilePath oss中的文件路径，包含文件名 例如abc/efg/123.jpg
     * @param file        需要上传的文件
     */
    public boolean upload(String bucketName, String ossFilePath, File file) {
        PutObjectRequest putObjectRequest = new PutObjectRequest(
                bucketName,
                ossFilePath,
                file);
        return upload(putObjectRequest);
    }

    /**
     * 获取临时下载链接
     *
     * @param bucketName   bucketName
     * @param ossFilePath  oss中的文件路径，包含文件名 例如abc/efg/123.jpg
     * @param expireMinute 多少分钟过期
     * @return URL
     */
    public String getDownloadUrl(String bucketName, String ossFilePath, long expireMinute) {
        LocalDateTime localDateTime = LocalDateTime.now().plusMinutes(expireMinute);
        Date expireDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        URL url = ossClient.generatePresignedUrl(bucketName, ossFilePath, expireDate);
        return url.toString();
    }

    /**
     * 获取临时下载链接
     *
     * @return URL
     */
    public String getDownloadUrl(GeneratePresignedUrlRequest request) {
        URL url = ossClient.generatePresignedUrl(request);
        return url.toString();
    }

    /**
     * 获取sts临时授权
     *
     * @param bucketName      bucketName
     * @param arn             arn
     * @param durationSeconds durationSeconds
     * @return RESP
     * @throws Exception E
     */
    public AssumeRoleResponse getSts(String bucketName, String arn, long durationSeconds) throws Exception {
        AssumeRoleRequest request = new AssumeRoleRequest();
        request.setMethod(MethodType.POST);
        // 从控制台RAM角色管理中获取
        request.setRoleArn(arn);
        // 这个名字随意
        request.setRoleSessionName(ROLE_SESSION_NAME);
        request.setProtocol(ProtocolType.HTTPS);
        String policy = POLICY.replace("bucketName", bucketName);
        request.setPolicy(policy);
        request.setDurationSeconds(durationSeconds);

        return acsClient.getAcsResponse(request);
    }

    public void destroy() {
        if (Objects.nonNull(ossClient)) {
            ossClient.shutdown();
            ossClient=null;
        }
    }

    private boolean upload(PutObjectRequest putObjectRequest) {
        PutObjectResult putObjectResult = null;
        try {
            putObjectResult = ossClient.putObject(putObjectRequest);
        } catch (Exception e) {
            log.error("上次文件失败",e);
        }
        String eTag = putObjectResult.getETag();
        return CharSequenceUtil.isNotBlank(eTag);
    }

    /**
     * 检查文件是否存在
     *
     * @param bucketName  桶名称
     * @param ossFilePath 文件全路径名称,如dir/path_x.csv
     * @return bool
     */
    public boolean checkExist(String bucketName, String ossFilePath) {
        return ossClient.doesObjectExist(bucketName, ossFilePath);
    }

    /**
     * 流式下载,适用于按行存储的文本类文件,如csv、txt或者日志文件
     *
     * @param bucketName       桶名称
     * @param ossFilePath      文件全路径名称,如dir/path_x.csv
     * @param lineProcessor    行处理器
     * @param progressListener 进度处理器
     */
    public void streamDownload(String bucketName,
                               String ossFilePath,
                               OssStreamDownloadLineProcessor lineProcessor,
                               ProgressListener progressListener) {
        // 文件存在的时候才下载
        if (checkExist(bucketName, ossFilePath)) {
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, ossFilePath);
            GetObjectRequest realGetObjectRequest = getObjectRequest.withProgressListener(progressListener);
            OSSObject ossObject = ossClient.getObject(realGetObjectRequest);
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(ossObject.getObjectContent()))) {
                int lineNumber = 0;
                for (; ; ) {
                    String line = reader.readLine();
                    if (Objects.isNull(line)) {
                        break;
                    }
                    lineNumber++;
                    // 处理行
                    lineProcessor.processLine(lineNumber, line);
                }
            } catch (Exception e) {
                log.warn("OSS桶文件流式下载异常,桶:{},文件:{}", bucketName, ossFilePath, e);
            }
        } else {
            log.warn("OSS桶文件不存在,桶:{},文件:{}", bucketName, ossFilePath);
        }
    }

    /**
     * 流式下载,适用于按行存储的文本类文件,如csv、txt或者日志文件
     *
     * @param bucketName    桶名称
     * @param ossFilePath   文件全路径名称,如dir/path_x.csv
     * @param lineProcessor 行处理器
     */
    public void streamDownload(String bucketName,
                               String ossFilePath,
                               OssStreamDownloadLineProcessor lineProcessor) {
        streamDownload(bucketName, ossFilePath, lineProcessor, ProgressListener.NOOP);
    }

    /**
     * 上传图片 - 附带图片样式,对图片进行二次加工和持久化
     *
     * @param bucketName  bucketName
     * @param ossFilePath ossFilePath,例如abc/efg/123.jpg
     * @param file        file
     * @param options     options
     * @return result
     */
    public boolean uploadPhotoWithStyle(String bucketName,
                                        String ossFilePath,
                                        File file,
                                        List<OssPhotoOption> options) {
        boolean uploadResult = upload(bucketName, ossFilePath, file);
        if (uploadResult) {
            return processOssPhotoFilePersistence(bucketName, ossFilePath, ossFilePath, options);
        }
        return false;
    }

    /**
     * 处理OSS图片持久化 - 自定义图片样式处理完后持久化结果
     *
     * @param bucketName        bucketName
     * @param sourceOssFilePath sourceOssFilePath
     * @param targetOssFilePath targetOssFilePath
     * @param options           options
     * @return result
     */
    public boolean processOssPhotoFilePersistence(String bucketName,
                                                  String sourceOssFilePath,
                                                  String targetOssFilePath,
                                                  List<OssPhotoOption> options) {
        String styleType = OssPhotoOptionsParser.X.parse(options);
        StringBuilder styleBuilder = new StringBuilder();
        Formatter styleFormatter = new Formatter(styleBuilder);
        styleFormatter.format("%s|sys/saveas,o_%s,b_%s", styleType,
                BinaryUtil.toBase64String(targetOssFilePath.getBytes()),
                BinaryUtil.toBase64String(bucketName.getBytes()));
        try {
            ProcessObjectRequest request = new ProcessObjectRequest(bucketName, sourceOssFilePath, styleBuilder.toString());
            GenericResult processResult = ossClient.processObject(request);
            ResponseMessage response = processResult.getResponse();
            InputStream content = response.getContent();
            content.close();
            return response.isSuccessful();
        } catch (Exception e) {
            log.error("上传附带图片样式的图片文件到OSS异常,sourceOssFilePath:{},targetOssFilePath:{}", sourceOssFilePath, targetOssFilePath, e);
        }
        return false;
    }

    /**
     * 上传图片 - 附带图片样式,对图片进行二次加工和持久化
     *
     * @param bucketName  bucketName
     * @param ossFilePath ossFilePath,例如abc/efg/123.jpg
     * @param inputStream inputStream
     * @param options     options
     * @return result
     */
    public boolean uploadPhotoWithStyle(String bucketName,
                                        String ossFilePath,
                                        InputStream inputStream,
                                        List<OssPhotoOption> options) {
        boolean uploadResult = upload(bucketName, ossFilePath, inputStream);
        if (uploadResult) {
            return processOssPhotoFilePersistence(bucketName, ossFilePath, ossFilePath, options);
        }
        return false;
    }

    /**
     * 删除单个文件
     *
     * @param bucketName  bucketName
     * @param ossFilePath ossFilePath
     */
    public void deleteSingleObject(String bucketName,
                                   String ossFilePath) {
        ossClient.deleteObject(bucketName, ossFilePath);
    }

    /**
     * 删除多个文件
     *
     * @param bucketName      bucketName
     * @param ossFilePathList ossFilePathList
     * @return delete objects
     */
    public List<String> deleteMultiObjects(String bucketName,
                                           List<String> ossFilePathList) {
        if (Objects.isNull(ossFilePathList) || ossFilePathList.size() > MAX_DELETE_OBJECTS) {
            throw new IllegalArgumentException("ossFilePathList must not be NULL or size must not be gt 1000");
        }
        DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName)
                .withKeys(ossFilePathList)
                .withEncodingType("url"));
        return deleteObjectsResult.getDeletedObjects();
    }
}
